package com.github.operationlog;

import com.github.operationlog.command.DeleteOperationCommand;
import com.github.operationlog.command.InsertOperationCommand;
import com.github.operationlog.command.OperationCommand;
import com.github.operationlog.command.UpdateOperationCommand;
import com.github.operationlog.output.OperationLogOutput;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Bean操作日志代理回调{@link MethodInterceptor}
 *
 * @author luke.luo
 */
@Slf4j
public class OperationLogMethodInterceptor implements MethodInterceptor {

    /**
     * 方法调用目标对象
     */
    private Object target;

    /**
     * 数据表名
     */
    private String table;

    /**
     * 根据ID查询实例方法
     */
    private Method findByIdMethod;

    /**
     * 日志数据输出对象
     */
    private OperationLogOutput operationLogOutput;

    /**
     * {@link MethodAttribute}映射表，key为方法名，value为{@link MethodAttribute}实例
     */
    private Map<String, MethodAttribute> methodAttributeTable = new ConcurrentHashMap<>();

    /**
     * 操作命令映射表，key为方法名，value为操作命令{@link OperationCommand}
      */
    private Map<String, OperationCommand> operationCommandTable = new ConcurrentHashMap<>();

    private final OperationCommandFactory operationCommandFactory = new OperationCommandFactory();

    public OperationLogMethodInterceptor(String table, OperationLogOutput operationLogOutput) {
        this.table = table;
        this.operationLogOutput = operationLogOutput;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public Object getTarget() {
        return target;
    }

    public void setFindByIdMethod(Method findByIdMethod) {
        this.findByIdMethod = findByIdMethod;
    }

    /**
     * 添加{@link MethodAttribute}，根据其生成操作命令
     *
     * @param methodAttribute {@link MethodAttribute}实例
     */
    public void addMethodAttribute(MethodAttribute methodAttribute) {
        if (methodAttribute == null || methodAttribute.getMethod() == null || methodAttribute.getType() == null) {
            log.warn("操作日志方法属性数据不完整, methodAttribute={}", methodAttribute);
            return;
        }

        methodAttribute.setName(methodAttribute.getMethod().getName());
        if (methodAttributeTable.containsKey(methodAttribute.getName())) {
            log.warn("操作日志方法{}-{}[{}]已配置，将保留旧配置",
                    methodAttribute.getTargetName(), methodAttribute.getName(), methodAttribute.getType());
            return;
        }
        methodAttributeTable.put(methodAttribute.getName(), methodAttribute);
    }

    public void afterPropertiesSet() {
        for (Map.Entry<String, MethodAttribute> entry : methodAttributeTable.entrySet()) {
            OperationCommand command = operationCommandFactory.createCommand(entry.getValue());
            if (command != null) {
                operationCommandTable.put(entry.getKey(), command);
            }
        }
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) {
        if (!methodAttributeTable.containsKey(method.getName()) || args.length == 0) {
            return ReflectionUtils.invokeMethod(method, this.target, args);
        }

        OperationCommand command = operationCommandTable.get(method.getName());
        return command != null ?
                command.execute(args)
                :
                ReflectionUtils.invokeMethod(method, this.target, args);
    }

    /**
     * 操作指令简单工厂
     */
    class OperationCommandFactory {
        OperationCommand createCommand(MethodAttribute methodAttribute) {
            switch (methodAttribute.getType()) {
                case INSERT:
                    return new InsertOperationCommand(table, target, methodAttribute, findByIdMethod, operationLogOutput);
                case DELETE:
                    return new DeleteOperationCommand(table, target, methodAttribute, findByIdMethod, operationLogOutput);
                case UPDATE:
                    return new UpdateOperationCommand(table, target, methodAttribute, findByIdMethod, operationLogOutput);
                case SELECT:
                    log.info("暂不支持SELECT类型的方法添加操作日志");
                default:
                    // do-nothing
            }
            return null;
        }
    }

    @Override
    public OperationLogMethodInterceptor clone() {
        OperationLogMethodInterceptor operationLogMethodInterceptor = new OperationLogMethodInterceptor(table, operationLogOutput);
        operationLogMethodInterceptor.target = target;
        operationLogMethodInterceptor.findByIdMethod = findByIdMethod;
        operationLogMethodInterceptor.methodAttributeTable = methodAttributeTable;
        operationLogMethodInterceptor.operationCommandTable = operationCommandTable;
        return operationLogMethodInterceptor;
    }
}
